﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Mpi.Common.Collections;
using System.Linq;

namespace Mpi.Common.Collections
{
    public abstract class ObservableIdCollectionBase<ObjectType, IdType> : List<ObjectType>
    {
        private bool _enableIdConstraint = false;
        public bool IdConstraintEnabled
        {
            get { return _enableIdConstraint; }
        }

        #region constructors
        public ObservableIdCollectionBase()
        {
        }

        public ObservableIdCollectionBase(IEnumerable<ObjectType> copy)
            :base(copy)
        {
        }

        public ObservableIdCollectionBase(bool idConstraintEnabled)
        {
            _enableIdConstraint = idConstraintEnabled;
        }
        #endregion constructors

        protected abstract IdType GetIdentifiere(ObjectType item);
        protected abstract void SetOwner(ObjectType item);
        protected abstract void RemoveOwner(ObjectType item);

        public IEnumerable<IdType> ListIdentifiers()
        {
            return this.Select(x => GetIdentifiere(x));
        }

        public IEnumerable<IdType> ListDistinctIdentifiers()
        {
            return ListIdentifiers().Distinct();
        }

        /// <summary>
        /// Indicates whether or not a item with given id is contained in this collection.
        /// </summary>
        /// <param name="id">The id to match.</param>
        /// <returns>A value indicating the contained property.</returns>
        public virtual bool Contains(IdType id)
        {
            return this.Find(id) != null;
        }

        /// <summary>
        /// Finds the first object with a given identifiere.
        /// </summary>
        /// <param name="id">The identifiere to match.</param>
        /// <returns>The matched item.</returns>
        public ObjectType Find(IdType id)
        {
            return base.Find(delegate(ObjectType ot) { return GetIdentifiere(ot).Equals(id); });
        }

        /// <summary>
        /// Finds all object with a specific identifiere.
        /// </summary>
        /// <param name="id">The identifiere to match.</param>
        /// <returns>A list of items.</returns>
        public List<ObjectType> FindAll(IdType id)
        {
            return base.FindAll(delegate(ObjectType ot) { return GetIdentifiere(ot).Equals(id); });
        }

        public void Add(ObjectType ot, bool takeOwnership)
        {
            if (this._enableIdConstraint && this.Contains(GetIdentifiere(ot)))
                throw new ArgumentException("Double key entry", "ObjectIdentifiere");
            base.Add(ot);
            if (takeOwnership)
                SetOwner(ot);
            OnItemAdded(ot);
        }

        public new void Add(ObjectType ot)
        {
            Add(ot, true);
        }

        public void Remove(IdType id)
        {
            List<ObjectType> ots = this.FindAll(id);
            foreach (ObjectType ot in ots)
            {
                this.Remove(ot);
            }
        }

        public new void Remove(ObjectType ot)
        {
            if (this.Contains(ot))
            {
                base.Remove(ot);
                RemoveOwner(ot);
                OnItemRemoved(ot);
            }
        }

        /// <summary>
        /// Removes a item at a specific index.
        /// </summary>
        /// <param name="indx">The index of the item to remove.</param>
        public new void RemoveAt(int indx)
        {
            ObjectType ot = this[indx];
            base.RemoveAt(indx);
            OnItemRemoved(ot);
        }

        /// <summary>
        /// Removes all items in this collection.
        /// </summary>
        public new void Clear()
        {
            foreach (ObjectType ot in this)
            {
                if (ot is INotifyPropertyChanged)
                {
                    (ot as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(OnItemPropertyChanged);

                }
            }
            base.Clear();
            OnCleared();
        }

        /// <summary>
        /// Moves a item up.
        /// </summary>
        /// <param name="ot"></param>
        public void MoveUp(ObjectType ot)
        {
            int indx = base.IndexOf(ot);
            if (indx > 0)
            {
                base.Remove(ot);
                base.Insert(indx - 1, ot);
                OnItemMoved(ot);
            }
        }

        /// <summary>
        /// Moves a item down.
        /// </summary>
        /// <param name="ot"></param>
        public void MoveDown(ObjectType ot)
        {
            int indx = base.IndexOf(ot);
            if (indx >= 0 && indx < this.Count - 1)
            {
                base.Remove(ot);
                base.Insert(indx + 1, ot);
                OnItemMoved(ot);
            }
        }

        public event EventHandler Cleared;
        public event EventHandler CollectionChanged;
        public event EventHandler<ItemEventArgs<ObjectType>> ItemAdded;
        public event EventHandler<ItemEventArgs<ObjectType>> ItemRemoved;
        public event EventHandler<ItemEventArgs<ObjectType>> ItemMoved;

        protected virtual void OnCollectionChanged()
        {
            if (CollectionChanged != null)
                CollectionChanged(this, new EventArgs());
        }

        protected virtual void OnCleared()
        {
            if (Cleared != null)
                Cleared(this, new EventArgs());
            OnCollectionChanged();
        }

        protected virtual void OnItemAdded(ObjectType item)
        {
            if (item is INotifyPropertyChanged)
            {
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
            }

            if (ItemAdded != null)
            {
                ItemEventArgs<ObjectType> iea = new ItemEventArgs<ObjectType>(item);
                ItemAdded(this, iea);
            }
            OnCollectionChanged();
        }

        void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnCollectionChanged();
        }

        protected virtual void OnItemRemoved(ObjectType item)
        {
            if (item is INotifyPropertyChanged)
            {
                (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(OnItemPropertyChanged);

            }
            if (ItemRemoved != null)
            {

                ItemEventArgs<ObjectType> iea = new ItemEventArgs<ObjectType>(item);
                ItemRemoved(this, iea);
            }
            OnCollectionChanged();
        }

        protected virtual void OnItemMoved(ObjectType item)
        {
            if (ItemMoved != null)
            {
                ItemEventArgs<ObjectType> iea = new ItemEventArgs<ObjectType>(item);
                ItemMoved(this, iea);
            }
            OnCollectionChanged();
        }
    }
}
